package com.wuyu.ibatis.builder;

import com.wuyu.ibatis.config.Configuration;
import com.wuyu.ibatis.constants.MapperConstants;
import com.wuyu.ibatis.exception.MybatisException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.InputStream;

/**
 * mapper构建
 */
public class XMLMapperBuilder {

    /**
     * mapper路径
     */
    private final String resource;

    /**
     * 配置信息
     */
    protected final Configuration configuration;

    /**
     * mapper构建帮助类
     */
    private final MapperBuilderAssistant builderAssistant;


    public XMLMapperBuilder(Configuration configuration, String resource) {
        this.resource = resource;
        this.configuration = configuration;
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    }

    /**
     * mapper 解析
     *
     * @throws IOException
     */
    public void parse() throws IOException {
        //已经装载
        if (configuration.isResourceLoaded(resource)) {
            return;
        }
        //解析mapper
        InputStream in = null;
        try {
            in = new ClassPathResource(resource).getInputStream();
            // 1,获取DocumentBuilderFactory实例
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            // 2,获取DocumentBuilder实例
            DocumentBuilder docBuilder = factory.newDocumentBuilder();
            // 3,将docBuilder转换为Document
            Document config = docBuilder.parse(in);
            // 4,配置mapper元素
            configurationElement(config.getDocumentElement());
            //5,添加mapper配置
            configuration.addLoadedResource(resource);
            //6,mapper命名空间绑定
            bindMapperForNamespace();
        } catch (Exception e) {
            throw new MybatisException(e);
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }

    /**
     * 构建mapper元素
     *
     * @param context
     */
    private void configurationElement(Element context) {
        try {
            String namespace = context.getAttribute(MapperConstants.NAMESPACE);
            if (namespace == null || namespace.isEmpty()) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            builderAssistant.setCurrentNamespace(namespace);
            buildStatement(context.getChildNodes());
        } catch (Exception e) {
            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
    }

    /**
     * 构建statement
     *
     * @param nodeList
     */
    private void buildStatement(NodeList nodeList) {
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node instanceof Element) {
                Element element = (Element) node;
                String nodeName = element.getNodeName();
                Assert.notNull(element, "Element must not be null");
                Assert.notNull(element.getNodeName(), "Element name must not be null");
                if (MapperConstants.SELECT.equals(nodeName) || MapperConstants.DELETE.equals(nodeName) || MapperConstants.UPDATE.equals(nodeName) || MapperConstants.INSERT.equals(nodeName)) {
                    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, element);
                }
            }
        }
    }

    /**
     * mapper命名空间绑定
     */
    private void bindMapperForNamespace() {
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class<?> boundType = null;
            try {
                boundType = ClassUtils.forName(namespace, ClassUtils.getDefaultClassLoader());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if (boundType != null && !configuration.hasMapper(boundType)) {
                configuration.addLoadedResource("namespace:" + namespace);
                configuration.addMapper(boundType);
            }
        }
    }

}
